
#include <UIPEthernet.h>

#define PORT 5000
#define LO 0
#define MIDL 1
#define MIDH 2
#define HI 3

EthernetClient client;
EthernetUDP udp;
uint16_t ports = 0;

uint32_t invert(uint32_t val)
{
  uint8_t *from = (uint8_t *)&val;
  uint32_t inv;
  uint8_t *to = (uint8_t *)&inv;

  to[LO] = from[HI];
  to[MIDL] = from[MIDH];
  to[MIDH] = from[MIDL];
  to[HI] = from[LO];

  return inv;
}

uint32_t getNetworkAddress(uint32_t myIp, uint32_t netMask)
{
  uint32_t address;
  uint8_t *pa = (uint8_t *)&address;
  uint8_t *pip = (uint8_t *)&myIp;
  uint8_t *pm = (uint8_t *)&netMask;

  pa[LO] = pip[LO] & pm[LO];
  pa[MIDL] = pip[MIDL] & pm[MIDL];
  pa[MIDH] = pip[MIDH] & pm[MIDH];
  pa[HI] = pip[HI] & pm[HI];

  return address;
}

uint32_t getIpRange(uint32_t netMask)
{
  uint32_t range;
  uint8_t *i = (uint8_t *)&range;
  uint8_t *pm = (uint8_t *)&netMask;

  i[LO] = 255 ^ pm[LO];
  i[MIDL] = 255 ^ pm[MIDL];
  i[MIDH] = 255 ^ pm[MIDH];
  i[HI] = 255 ^ pm[HI];
  
  return range;
}
  
uint32_t current = 0;
void printNext(uint32_t netAddress, uint32_t range)
{
  uint8_t *netAddr = (uint8_t *)&netAddress;
  uint8_t *curr = (uint8_t *)&current;
  uint8_t addrHI,addrMIDH,addrMIDL,addrLO;

  addrHI = netAddr[HI]+curr[HI];
  addrMIDH = netAddr[MIDH]+curr[MIDH];
  addrMIDL = netAddr[MIDL]+curr[MIDL];
  addrLO = netAddr[LO]+curr[LO];

  Serial.print(addrHI);
  Serial.print(addrMIDH);
  Serial.print(addrMIDL);
  Serial.println(addrLO);

  int res = udp.beginPacket(IPAddress(addrHI,addrMIDH,addrMIDL,33),5000);
  while(res)
  {
    res = udp.write("hello world from arduino");
    if(!res) break;
    res = udp.endPacket();
    if(!res) break;
    if(current == range) current = 0;
    else current++;
    break;
  }
  udp.stop();
}

uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};
void initNetDHCP(uint32_t *myIp, uint32_t *netMask, uint32_t *netAddress, uint32_t *range)
{
  Ethernet.begin(mac);

  Serial.print("localIP: ");
  Serial.println(Ethernet.localIP());
  Serial.print("subnetMask: ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("gatewayIP: ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("dnsServerIP: ");
  Serial.println(Ethernet.dnsServerIP());

  uint8_t *ptrIp = (uint8_t *)myIp;
  ptrIp[HI] = Ethernet.localIP()[0];
  ptrIp[MIDH] = Ethernet.localIP()[1];
  ptrIp[MIDL] = Ethernet.localIP()[2];
  ptrIp[LO] = Ethernet.localIP()[3];
  
  uint8_t *ptrMask = (uint8_t *)netMask;
  ptrMask[HI] = Ethernet.subnetMask()[0];
  ptrMask[MIDH] = Ethernet.subnetMask()[1];
  ptrMask[MIDL] = Ethernet.subnetMask()[2];
  ptrMask[LO] = Ethernet.subnetMask()[3];

  *netAddress = getNetworkAddress(*myIp,*netMask);
  *range = getIpRange(*netMask);
}

void initNetBCast(uint32_t bcastAddr)
{
  uint8_t *ptrIp = (uint8_t *)&bcastAddr;

  Serial.print(ptrIp[HI]);
  Serial.print(ptrIp[MIDH]);
  Serial.print(ptrIp[MIDL]);
  Serial.println(ptrIp[LO]);

  Ethernet.begin(mac,IPAddress(ptrIp[HI],ptrIp[MIDH],ptrIp[MIDL],ptrIp[LO]));

  Serial.print("localIP: ");
  Serial.println(Ethernet.localIP());
  Serial.print("subnetMask: ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("gatewayIP: ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("dnsServerIP: ");
  Serial.println(Ethernet.dnsServerIP());
}

void sendNoIpContent()
{
  int wait;
  const char hostname[] = "carlosefml.no-ip.org";
  const char auth[] = "Y2FybG9zZWZtbEBnbWFpbC5jb206Q2FEdTQxU2VS==";

  client.print("GET /nic/update?hostname="); client.print(hostname); client.println(" HTTP/1.0");
  client.println("Host: dynupdate.no-ip.com");
  client.print("Authorization: Basic "); client.println(auth);
  client.println("User-Agent: Cefml Arduino Client/1.0 carlosefml@gmail.com");
  client.println("");
  for(wait=0;wait<50;wait++) {
    if(client.available()) break;
    delay(100);
  }
  while(client.available()) {
    char c = client.read();
    Serial.print(c);
  }
}

/*
int getHostByName(const char* host, IPAddress& address)
{
  DNSClient dns;

  dns.begin(Ethernet.dnsServerIP());
  return dns.getHostByName(host, address);
}
*/

void noIpUpdate()
{
//  IPAddress address;
  
//  if(!getHostByName("dynupdate.no-ip.com",address)) return;

//  if(client.connect(address,80))
//  if(client.connect(IPAddress(8,23,224,120),80))
  if(client.connect("dynupdate.no-ip.com",80))
  {
    sendNoIpContent();
    client.stop();
  }
}

#define FIRST_PORT 0
#define LAST_PORT 13
uint16_t validPorts = 0b0000001111111000;
#define validPort(port) ( validPorts & (1<<(port)) )
void initPorts()
{
  int i;

  ports = 0;
  for(i=FIRST_PORT; i<=LAST_PORT; i++)
  {
    if(validPort(i))
    {
      pinMode(i, OUTPUT);
      digitalWrite(i, LOW);
    }
  }
}

void portOn(int port)
{
  if( validPort(port) )
  {
    ports |= 1 << port;
    digitalWrite(port,HIGH);
  }
}

void portOff(int port)
{
  if( validPort(port) )
  {
    ports &= ~(1 << port);
    digitalWrite(port,LOW);
  }
}

void parseCommand(uint8_t *command, int len)
{
  uint32_t *cmd = (uint32_t *)command;

  if(len<8) return;

  switch(cmd[0])
  {
    case 0: portOff(cmd[1]); break;
    case 1: portOn(cmd[1]); break;
  }
}

uint32_t netAddress, range;
void setup() {
  uint32_t myIp, netMask;

  Serial.begin(9600);

  initPorts();
  initNetDHCP(&myIp,&netMask,&netAddress,&range);
  delay(1000);
//  initNetBCast(netAddress+range);
}

void readUdpPacket()
{
  uint8_t buf[256];
  int len = 0;
  int received=0;

  udp.begin(PORT);
  int size = udp.parsePacket();
  while(size>0)
  {
    received++;
    if(received>512) break;

    len += udp.read(buf+len,256-len);
    if(len==256) break;
    size = udp.available();
  }

  if(received)
  {
    udp.flush();
    parseCommand(buf,len);
    udp.beginPacket(udp.remoteIP(),udp.remotePort());
    int port;
    for(port=FIRST_PORT; port<=LAST_PORT; port++)
    {
      int enabled = ports & (1 << port);
      if(enabled) udp.print("1");
      else udp.print("0");
    }
    udp.println("");
    udp.println("OK");
    udp.print("\0");
    udp.endPacket();
  }

  udp.stop();
}

int loopCnt = 0;
void loop()
{
//  printNext(netAddress,range);
  if( !(loopCnt%10000) ) {
    noIpUpdate();
  }
  
  readUdpPacket();

  delay(100);
  ++loopCnt;
}

